/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


import { ArkMethod, ArkFile } from "arkanalyzer/lib";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { Defects, IssueReport } from "../../model/Defects";
import { ClassMatcher, FileMatcher, MatcherCallback, MatcherTypes, MethodMatcher } from "../../matcher/Matchers";
import { CheckerUtils } from "../../utils/checker/CheckerUtils";
import { Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";
const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'RequireAwaitCheck');
const gmetaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: "docs/require-await-check.md", // TODO: support url
    description: "Disallow async functions which have no `await` expression."
};
export class RequireAwaitCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gmetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE,
    };

    private classMatcher: ClassMatcher = {
        file: [this.fileMatcher],
        matcherType: MatcherTypes.CLASS
    };
    private methodMatcher: MethodMatcher = {
        matcherType: MatcherTypes.METHOD,
        class: [this.classMatcher]
    };
    public registerMatchers(): MatcherCallback[] {
        const matchMethod: MatcherCallback = {
            matcher: this.methodMatcher,
            callback: this.check
        }
        return [matchMethod];
    }
    /**
     * 判断字符串中是否包含await关键字
     *
     * @param text 要判断的字符串
     * @returns 如果字符串中包含await关键字，则返回true；否则返回false
     */
    private containsAwaitKeyword(text: string) {
        return /await\b/.test(text);
    }
    /**
     * 判断给定的文本中是否包含异步关键字 'async'
     *
     * @param text 待检查的文本
     * @returns 如果文本中包含 'async' 关键字，则返回 true，否则返回 false
     */
    private containsAsyncKeyword(text: string) {
        return /async\b/.test(text);
    }
    /**
     * 检查文本中是否包含 "catch" 关键字
     *
     * @param text 需要检查的文本
     * @returns 如果文本中包含 "catch" 关键字，则返回 true，否则返回 false
     */
    private containsCatchKeyword(text: string) {
        return /catch\b/.test(text);
    }
    /**
     * 判断字符串中是否包含 'then' 关键字
     *
     * @param text 要检查的字符串
     * @returns 如果字符串中包含 'then' 关键字，则返回 true；否则返回 false
     */
    private containsThenKeyword(text: string) {
        return /then\b/.test(text);
    }
    /**
     * 判断字符串中是否包含关键词 "resolve"
     *
     * @param text 待判断的字符串
     * @returns 如果字符串中包含关键词 "resolve"，则返回 true，否则返回 false
     */
    private containsResolveKeyword(text: string) {
        return /resolve\b/.test(text);
    }
    /**
     * 检查字符串中是否包含拒绝关键字
     *
     * @param text 待检查的字符串
     * @returns 如果包含拒绝关键字则返回 true，否则返回 false
     */
    private containsRejectKeyword(text: string) {
        return /reject\b/.test(text);
    }
    /**
     * 检查字符串中是否包含 "Promise" 关键字
     *
     * @param text 要检查的字符串
     * @returns 如果字符串中包含 "Promise" 关键字，则返回 true；否则返回 false
     */
    private containsPromiseKeyword(text: string) {
        return /Promise\b/.test(text);
    }
    /**
     * 判断给定的字符串中是否包含"unknown" 关键字
     *
     * @param text 要检查的字符串
     * @returns 如果包含未知的关键词，则返回 true；否则返回 false
     */
    private containsUnknownKeyword(text: string) {
        return /unknown\b/.test(text);
    }
    /**
     * 判断字符串中是否包含 'void' 关键字
     *
     * @param text 要检查的字符串
     * @returns 如果包含 'void' 关键字，则返回 true；否则返回 false
     */
    private containsVoidKeyword(text: string) {
        return /void\b/.test(text);
    }
    public check = (method: ArkMethod) => {
        // 获取方法体的语句列表
        const stmts = method.getBody()?.getCfg().getStmts() ?? [];
        // 获取方法的代码
        const code = method.getCode() ?? '';
        // 获取方法所属的类
        let declareClass = method.getDeclaringArkClass()
        // 获取类所属的文件
        const arkFile = declareClass.getDeclaringArkFile();
        // 初始化异步文本变量
        let asyncText = ''
        // 检查函数体是否为空或者是否包含过多的语句，如果是则直接返回
        if (stmts.length === 0 || stmts.length >= 10) {
            return;
        }
        //如果代码中不包含async关键字，则直接返回
        if (!this.containsAsyncKeyword(code)) {
            return;
        }
        for (const stmt of stmts) {
            // 从语句中获取调用表达式
            const invokeExpr = CheckerUtils.getInvokeExprFromStmt(stmt)
            if (!invokeExpr) {
                // 如果代码中包含async关键字，则记录异步文本
                if (this.containsAsyncKeyword(code)) {
                    asyncText = code;
                }
                // 如果存在异步文本，则进行正则匹配
                if (asyncText) {
                    const regex = /\basync\s+([^(]+)/g;
                    const match = asyncText.match(regex)
                    if (match) {
                        const asyncFunction = match[0];
                        let text = stmt.getOriginalText() ?? '';
                        // 如果语句文本存在且不包含await关键字
                        if (text && !this.containsAwaitKeyword(text)) {
                            // 如果语句文本不包含catch、then、reject、resolve关键字
                            if (!this.containsCatchKeyword(text) && !this.containsThenKeyword(text)) {
                                // 如果语句文本不包含reject,resolve关键字
                                if (!this.containsRejectKeyword(text) && !this.containsResolveKeyword(text)) {
                                    this.addIssueReport(arkFile, asyncFunction);
                                }
                            }
                        }
                    }
                }
                continue;
            }
            if (this.containsAsyncKeyword(code)) {
                asyncText = code;
            }
            if (asyncText) {
                const regex = /\basync\s+([^(]+)/g;
                const match = asyncText.match(regex)
                if (match) {
                    const asyncFunction = match[0];
                    // 获取调用表达式的方法签名
                    const methodSign = invokeExpr.getMethodSignature();
                    // 获取方法签名的子签名
                    const subSignature = methodSign.getMethodSubSignature();
                    // 获取子签名的返回类型
                    const returnType = subSignature.getReturnType();
                    // 获取返回类型的字符串表示
                    const returnTypeString = returnType.getTypeString();
                    let text = stmt.getOriginalText() ?? '';
                    // 如果语句文本不包含await关键字
                    if (!this.containsAwaitKeyword(text)) {
                        // 如果语句文本不包含catch、then、reject、resolve关键字
                        if (!this.containsCatchKeyword(text) && !this.containsThenKeyword(text)) {
                            if (!this.containsRejectKeyword(text) && !this.containsResolveKeyword(text)) {
                                // 如果返回类型包含Promise、Unknown或Void关键字
                                if (this.containsPromiseKeyword(returnTypeString) || this.containsUnknownKeyword(returnTypeString) || this.containsVoidKeyword(returnTypeString)) {
                                    this.addIssueReport(arkFile, asyncFunction);
                                }
                            }
                        }
                    }
                }
            }
            if (asyncText) {
                const regex = /\basync\s*\(\s*\)\s*/g;
                const match = asyncText.match(regex)
                if (match) {
                    const asyncFunction = match[0];
                    const methodSign = invokeExpr.getMethodSignature();
                    const subSignature = methodSign.getMethodSubSignature()
                    const returnType = subSignature.getReturnType();
                    const returnTypeString = returnType.getTypeString();
                    let text = stmt.getOriginalText() ?? '';
                    if (!this.containsAwaitKeyword(text)) {
                        if (!this.containsCatchKeyword(text) && !this.containsThenKeyword(text)) {
                            if (!this.containsRejectKeyword(text) && !this.containsResolveKeyword(text)) {
                                if (this.containsPromiseKeyword(returnTypeString) || this.containsUnknownKeyword(returnTypeString) || this.containsVoidKeyword(returnTypeString)) {
                                    this.addIssueReport(arkFile, asyncFunction);
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    private addIssueReport(arkFile: ArkFile, name: string) {
        const severity = this.rule.alert ?? this.metaData.severity;
        const warnInfo = this.getLineAndColumn(arkFile, name);
        this.metaData.description = this.metaData.description.padEnd(130);
        const fixKey = warnInfo.line + ':' + warnInfo.startCol;
        const mergeKey = warnInfo.filePath + '%' + fixKey + '%' + this.metaData.description + this.rule.ruleId;
        const defect = new Defects(warnInfo.line, warnInfo.startCol, this.metaData.description, severity, this.rule.ruleId,
            mergeKey, this.metaData.ruleDocPath, true, false, false, fixKey);
        this.issues.push(new IssueReport(defect, undefined));
        RuleListUtil.push(defect);
    }
    private getLineAndColumn(arkFile: ArkFile, name: string) {
        // 判断arkFile是否存在
        if (arkFile) {
            // 获取arkFile的代码
            const code = arkFile.getCode() ?? '';
            // 将代码按行分割
            const lines = code.split('\r\n');
            let lineNumber = 1;
            // 遍历每一行代码
            for (const line of lines) {
                // 在当前行中查找name的索引
                const lineIndex = line.indexOf(name);
                if (lineIndex !== -1) {
                    const startCol = lineIndex + 1; // 列号从1开始
                    // 获取arkFile的真实路径
                    const originPath = arkFile.getFilePath();
                    // 返回包含行号、列号和文件路径的对象
                    return { line: lineNumber, startCol, filePath: originPath }
                }
                // 行号递增
                lineNumber++;
            }
        } else {
            // 如果arkFile不存在，记录日志
            logger.debug('originStmt or arkFile is null');
        }
        // 返回默认的对象，表示未找到name
        return { line: -1, startCol: -1, endCol: -1, filePath: '' };
    }
}